#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "collaboration.h"
#include "dbAccessor.h"
#include "dbConnector.h"
#include "MIApplication.h"
#include "MIUser.h"
#include "MIRole.h"

DBAccessor::DBAccessor()
{
	conexion = new DBConnector(__HOST_DB__, __USER_DB__, __PASSWORD_DB__, __DB_DB__);
	conexion->open();
}

DBAccessor::~DBAccessor()
{
	conexion->close();
	delete conexion;
}

void DBAccessor::clearDB()
{	
	conexion->update("DELETE FROM application");
	conexion->update("DELETE FROM component");
	conexion->update("DELETE FROM allowed_roles");
	conexion->update("DELETE FROM allowed_users");
	conexion->update("DELETE FROM allowed_user_roles");
	conexion->update("DELETE FROM role_permissions");
	conexion->update("DELETE FROM user_permissions");
}
	
Collaboration::ListOfApplications DBAccessor::get()
{	
	Collaboration::ListOfApplications datos_aplicaciones;
	
	// Estos DB_RES se utilizaran para almacenar los datos recuperados de
	// la bd mediante consultas
	DB_RES* aplicaciones = NULL;
	DB_RES* roles = NULL;
	DB_RES* usuarios = NULL;
	DB_RES* componentes = NULL;

	DB_ROW row;
		
	char* command = (char*)malloc(512*sizeof(char));

	/*
	 * 1) recuperar todas las aplicaciones
	 */

	aplicaciones = conexion->query("SELECT * FROM application");
	for (int i=0; (row = db_fetch_row(aplicaciones)) != NULL; i++)
	{		
		/*
		 * 1.1) Recuperamos los datos propios de cada aplicacion
		 */
		Collaboration::MIApplication aplicacion;
		aplicacion.name = row[1];
		aplicacion.defaultPermissions = atoi(row[2]);

		int idApp = atoi(row[0]);
		aplicacion.identifier = idApp;

		/*
		 * 1.2) Recuperamos los datos de relaciones
		 */
		
		// a) Recuperamos los componentes asociados a la aplicacion
		// actual
		
		sprintf(command, "SELECT * FROM component WHERE id_application=%d", idApp);
		
		componentes = conexion->query(command);
		
		while ((row = db_fetch_row(componentes))!=NULL)
		{
			Collaboration::MIComponent componente;
			componente.name = row[1];
			componente.permissions = aplicacion.defaultPermissions;
			componente.identifier = atoi(row[0]);
			
			MIApplication::addComponent(aplicacion, componente);
		}
				
		db_free_result(componentes);
		
		// b) Recuperamos los roles asociados a la aplicacion actual
		sprintf(command, 
				"SELECT role.id_role, role.name FROM allowed_roles, role WHERE allowed_roles.id_role=role.id_role AND allowed_roles.id_application=%d", 
				idApp);
		
		roles = conexion->query(command);

		while ((row = db_fetch_row(roles))!=NULL)
		{
			Collaboration::MIRole rol;
			rol.name = row[1];
			int id_rol = atoi(row[0]);
			rol.identifier = id_rol;

			// Buscamos los componente sobre los que el rol tiene
			// permisos
			sprintf(command, 
					"SELECT * FROM role_permissions, component WHERE role_permissions.id_role=%d AND role_permissions.id_component=component.id_component", 
					id_rol);
					
			componentes = conexion->query(command);

			while ((row = db_fetch_row(componentes))!=NULL)
			{
				MIRole::setComponentPermissions(rol, row[4], atoi(row[2]));
			}

			MIApplication::addRole(aplicacion, rol);
			
			db_free_result(componentes);
		}

		db_free_result(roles);

		// c) Recuperamos los usuarios asociados a la aplicacion actual
		sprintf(command,
				"SELECT * FROM allowed_users, user WHERE allowed_users.id_user = user.id_user AND allowed_users.id_application=%d",
				idApp);
		
		usuarios = conexion->query(command);

		while ((row = db_fetch_row(usuarios))!=NULL)
		{
			Collaboration::MIUser usuario;
					
			usuario.name = row[3];
			usuario.password = row[4];
			usuario.isOnline = false;	
			int id_usuario = atoi(row[0]);
			usuario.identifier = id_usuario;
			int id_rol_defecto = atoi(row[5]);
			bool admin = ( atoi(row[6]) == 0 ) ? false : true;
 
			usuario.isAdmin = admin;

			// buscamos el rol por defecto
			sprintf(command,
					"SELECT * FROM role WHERE role.id_role=%d",
					id_rol_defecto);
					
			roles = conexion->query(command);

			while ((row = db_fetch_row(roles))!=NULL)
			{
				usuario.defaultRole = row[1];
			}
			
			db_free_result(roles);

			// Buscamos los componente sobre los que el rol tiene
			// permisos
			sprintf(command,
					"SELECT * FROM user_permissions, component WHERE user_permissions.id_user=%d AND user_permissions.id_component=component.id_component",
					id_usuario);
					
			componentes = conexion->query(command);
			
			while ((row = db_fetch_row(componentes))!=NULL)
			{	
				MIUser::setComponentPermissions(usuario, row[4], atoi(row[2]));
			}
			
			db_free_result(componentes);

			// buscamos los roles permitidos
			sprintf(command,
					"SELECT * FROM allowed_user_roles, role WHERE allowed_user_roles.id_role = role.id_role AND allowed_user_roles.id_user=%d",
				   id_usuario);
			
			roles = conexion->query(command);

			while ((row = db_fetch_row(roles))!=NULL)
			{
				MIUser::addAllowedRole(usuario, row[3]);
			}
			
			db_free_result(roles);

			MIApplication::addUser(aplicacion, usuario);
		}
		
		db_free_result(usuarios);

		datos_aplicaciones.push_back(aplicacion);
	}
	
	db_free_result(aplicaciones);

	free(command);

	return datos_aplicaciones;
}

void DBAccessor::set(Collaboration::ListOfApplications& apps)
{
	clearDB();
	
	DB_RES* rs = NULL;
	DB_ROW row;
	Collaboration::MIApplication aplicacion;
	Collaboration::MIRole rol;
	Collaboration::MIUser usuario;
	Collaboration::MIComponent componente;
	
	Collaboration::ListOfRoles v_roles;
	Collaboration::ListOfStrings v_role_names;
	Collaboration::ListOfUsers v_users;
	Collaboration::ListOfComponents v_comps;
	
	unsigned int i, j, k;
	char* command = (char*)malloc(512*sizeof(char));

	for (i = 0; i < apps.size(); i++)
	{ // Para cada aplicacion
		aplicacion = apps[i];

		int id_aplicacion = i + 1;

		// Aplicacion
		::std::string nnn = aplicacion.name;
		sprintf(command, "INSERT INTO application VALUES(%d, \'%s\', %d)",
			id_aplicacion, nnn.c_str(), aplicacion.defaultPermissions);
			
		conexion->update(command);

		// Roles definidos
		v_roles = aplicacion.roles;
		
		for (j = 0; j < v_roles.size(); j++)
		{
			int id_rol = j + 1;
			::std::string nombre_rol = v_roles[j].name;

			// comprobar si existe en la tabla de rol, si no existe,
			// agregarlo
			sprintf(command, 
					"SELECT id_role FROM role WHERE name=\'%s\'",
				    nombre_rol.c_str());
			
			rs = conexion->query(command);
				
			if ( (row = db_fetch_row(rs)) != NULL )
				id_rol = atoi(row[0]);
			else
			{
				id_rol = conexion->getUID("role", "id_role");
				sprintf(command, 
						"INSERT INTO role VALUES(%d, \'%s\')",
						id_rol, nombre_rol.c_str());
						
				conexion->update(command);
			}

			db_free_result(rs);

			sprintf(command,
					"INSERT INTO allowed_roles VALUES(%d,%d)",
					id_rol, id_aplicacion);
					
			conexion->update(command);
		}

		// Usuarios definidos
		v_users = aplicacion.users;
		for (j = 0; j < v_users.size(); j++)
		{
			usuario = v_users[j];
			int id_usuario = j + 1;
			int administrador = usuario.isAdmin ? 1 : 0;
			::std::string nombre_usuario = usuario.name;
			::std::string clave = usuario.password;
			::std::string dr = usuario.defaultRole;
			
			sprintf(command,
					"SELECT id_role FROM role WHERE name=\'%s\'",
					dr.c_str());
					
			rs = conexion->query(command);
					
			int id_rol_predeterminado;
			if ((row = db_fetch_row(rs)) != NULL)
			{
				id_rol_predeterminado = atoi(row[0]);
			}
			
			else
			{
				id_rol_predeterminado = conexion->getUID("role", "id_role");
				sprintf(command,
					"INSERT INTO role VALUES(%d,\'%s\')",
					id_rol_predeterminado, dr.c_str());
					
				conexion->update(command);
			}
			
			db_free_result(rs);

			// comprobar si existe el usuario
			sprintf(command,
					"SELECT id_user FROM user WHERE name=\'%s\'",
				     nombre_usuario.c_str());
				
			rs = conexion->query(command);
			
			if ( (row = db_fetch_row(rs)) != NULL )
				id_usuario = atoi(row[0]);
				
			else // insertarlo
			{
				id_usuario = conexion->getUID("user", "id_user");
				sprintf(command,
						"INSERT INTO user VALUES(%d,\'%s\',\'%s\',%d, %d)",
						id_usuario, nombre_usuario.c_str(), clave.c_str(), id_rol_predeterminado, administrador);
				
				conexion->update(command);
			}

			db_free_result(rs);

			sprintf(command,
					"INSERT INTO allowed_users VALUES(%d, %d)",
					id_usuario, id_aplicacion);
					
			conexion->update(command);
		}

		// Componentes definidos
		v_comps = aplicacion.components;
		for (j = 0; j < v_comps.size(); j++)
		{
			componente = v_comps[j];
			int id_componente = j + 1;
			
			::std::string n_comp = componente.name;
			
			sprintf(command,
					"INSERT INTO component VALUES(%d, \'%s\', %d)",
					id_componente, n_comp.c_str(), id_aplicacion);
			
			conexion->update(command);
		}
		
		// Permisos especificados a los roles sobre componentes
		v_roles = aplicacion.roles;
		for (j = 0; j < v_roles.size(); j++)
		{
			rol = v_roles[j];
			int id_rol = j + 1;
			v_comps = rol.components;
			for (k = 0; k < v_comps.size(); k++)
			{
				componente = v_comps[k];
				const char* c_name = componente.name.c_str();
				
				sprintf(command,
						"SELECT id_component FROM component WHERE name=\'%s\' AND id_application=\'%d\'",
						c_name, id_aplicacion);
				
				rs = conexion->query(command);
				
				int id_componente;
				
				if ( (row = db_fetch_row(rs)) != NULL)
				{
					id_componente = atoi(row[0]);
				}
				else
				{
					id_componente = conexion->getUID("component", "id_component");
					sprintf(command,
							"INSERT INTO component VALUES(%d, \'%s\', %d)",
							id_componente, c_name, id_aplicacion);
							
					conexion->update(command);
				}
				
				db_free_result(rs);

				sprintf(command,
						"INSERT INTO role_permissions VALUES(%d, %d, %d)",
						id_rol, id_componente, componente.permissions);
						
				conexion->update(command);
			}
		}

		// Permisos especificados a los usuarios sobre componentes
		v_users = aplicacion.users;
		for (j = 0; j < v_users.size(); j++)
		{
			usuario = v_users[j];
			v_comps = usuario.components;
			int id_usuario = j + 1;
			for (k = 0; k < v_comps.size(); k++)
			{
				componente = v_comps[k];
				const char* c_name = componente.name.c_str();
				
				sprintf(command,
						"SELECT id_component FROM component WHERE name=\'%s\' AND id_application=%d",
						 c_name, id_aplicacion);
						
				rs = conexion->query(command);
				int id_componente;	
				if ( (row = db_fetch_row(rs)) != NULL)
				{
					id_componente = atoi(row[0]);
				}
				else
				{
					id_componente = conexion->getUID("component", "id_component");
					sprintf(command,
							"INSERT INTO component VALUES(%d, \'%s\', %d)",
							id_componente, c_name, id_aplicacion);
							
					conexion->update(command);
				}
				
				db_free_result(rs);

				sprintf(command,
						"INSERT INTO user_permissions VALUES(%d, %d, %d)",
						id_usuario, id_componente, componente.permissions);
						
				conexion->update(command);
			}
		}

		// Roles permitidos a los usuarios
		v_users = aplicacion.users;
		for (j = 0; j < v_users.size(); j++)
		{
			usuario = v_users[j];
			int id_usuario = j + 1;
			
			v_role_names = usuario.allowedRoles;
			for (k = 0; k < v_role_names.size(); k++)
			{
				const char* aux = v_role_names[k].c_str();
				
				sprintf(command,
						"SELECT id_role FROM role WHERE name=\'%s\'", aux);
						
				rs = conexion->query(command);
				int id_rol;
				
				if ( (row = db_fetch_row(rs)) != NULL )
				{
					id_rol = atoi(row[0]);
				}
				else
				{
					id_rol = conexion->getUID("role", "id_role");
					sprintf(command,
						"INSERT INTO role VALUES(%d,\'%s\')",
						id_rol, aux);
						
					conexion->update(command);
				}
				
				db_free_result(rs);

				sprintf(command,
						"INSERT INTO allowed_user_roles VALUES(\'%d\',\'%d\')",
						id_rol, id_usuario);
						
				conexion->update(command);
			}
		}
	}
	
	free(command);
}
	
void DBAccessor::createLog()
{
	time_t t = time(0);
	struct tm* tiempo = localtime(&t);
	
	int anho = tiempo->tm_year+1900;
	int mes = tiempo->tm_mon+1;
	int dia = tiempo->tm_mday;
	int hora = tiempo->tm_hour;
	int minuto = tiempo->tm_min;
	int segundo = tiempo->tm_sec;

	char* command = (char*)malloc(512*sizeof(char));
	char* fecha = (char*)malloc(128*sizeof(char));
	
	sprintf(fecha, "%d-%d-%d %d:%d:%d", anho, mes, dia, hora, minuto, segundo);
	sprintf(command, "INSERT INTO initlog VALUES(\'%s\')", fecha);
	
	conexion->update(command);
	
	free(fecha);
	free(command);
	free(tiempo);
}